/*
 * Copyright © 2017 eqxiu.com 北京中网易企秀科技有限公司  All rights reserved.
 */
package cn.hermit.data.xmljson.handler;

import cn.hermit.data.json.model.JSONFactory;
import cn.hermit.data.json.model.JSONObject;
import cn.hermit.util.StringUtils;
import cn.hermit.data.xml.ns.NamespaceContext;
import cn.hermit.data.xmljson.Badgerfish;

import javax.xml.XMLConstants;
import javax.xml.namespace.QName;
import java.util.Map;

/**
 * Content handler for conversion from XML to JSON with Badgerfish convention.
 * 
 * <p>
 * As there is an overkill and lost for white space in text node, the handler
 * provides a loosen Badgerfish convention against the official Badgerfish. We
 * called them loosen Badgerfish and strict Badgerfish.
 * </p>
 * 
 * @author Jack Gao (Chinese name : GAO JIANGUO, Email : linux.gjg@gmail.com)
 * @date 1 Jul, 2014
 */
public class X2JBFHandler extends X2JHandler {

	/**
	 * Indicate if use strict Badgerfish style
	 */
	private boolean strictBadgerfish = true;

	/**
	 * For strict Badgerfish style, indicate if use lightweight name space
	 * definition in JSON object.
	 */
	private boolean lightWeightXmlns;

	// FIX REST-885: If setHeader it false, only when render xml Array to json, the
	// result should be
	// arr:[{},{}], not arr:{}, arr#1:{}
	private boolean keepOrderOfMixedTexts;

	/**
	 * Constructor of Content handler for conversion from XML to JSON.
	 * Initialized an ordered JSON factory.
	 */
	public X2JBFHandler() {
		super();
	}

	/**
	 * Constructor of Content handler for conversion from XML to JSON.
	 * 
	 * @param jsonFactory
	 *            JSON Factory to create XML objects. If it is null, will
	 *            initialize an ordered JSON factory.
	 */
	public X2JBFHandler(JSONFactory jsonFactory) {
		super(jsonFactory);
	}

	/**
	 * Convert name space Attribute to JSON objects.
	 * 
	 * @param name
	 *            QName of the Attribute
	 * @param value
	 *            Value of the Attribute
	 */
	@Override
	protected void nsAttribute(QName name, String value) {
		if (!strictBadgerfish) {
			super.nsAttribute(name, value);
		} else if (lightWeightXmlns) {
			JSONObject parent = (JSONObject) stack.peek();
			JSONObject xmlns = getXmlnsObject(parent);
			super.appendToParent(xmlns, getNamespaceKey(name),
					super.jsonFactory.createValue(value));
		}
	}

	/**
	 * Get JSON Object of XML name space definition from parent JSON Object.
	 * 
	 * @param parent
	 *            The parent JSON Object
	 * @return JSON Object of XML name space definition
	 */
	private JSONObject getXmlnsObject(JSONObject parent) {
		JSONObject xmlns = (JSONObject) parent.getValue(Badgerfish.KEY_XMLNS);
		if (xmlns == null) {
			xmlns = super.jsonFactory.createObject();
			super.appendToParent(parent, Badgerfish.KEY_XMLNS, xmlns);
			super.jsonFactory.objectEntry(parent, Badgerfish.KEY_XMLNS, xmlns);
		}
		return xmlns;
	}

	/**
	 * Post handling for JSON objects generated by the XML Element.
	 * 
	 * @param name
	 *            The XML Element
	 * @param closedTag
	 *            If true, the XML Element is tag-closed
	 */
	@Override
	public void endElement(QName name, boolean closedTag) {
		if (!this.ignoredNamespace) {
			if (strictBadgerfish && !lightWeightXmlns) {
				JSONObject parent = (JSONObject) stack.peek();
				JSONObject xmlns = getXmlnsObject(parent);
				cn.hermit.data.xml.ns.QName nm = (cn.hermit.data.xml.ns.QName) name;
				NamespaceContext nsContext = nm.getNamespaceContext();
				Map<String, String>[] nsArr = nsContext.getNamespaces();
				for (int i = nsArr.length - 1; i >= 0; i--) {
					Map<String, String> nsMap = nsArr[i];
					for (String key : nsMap.keySet()) {
						super.appendToParent(xmlns, getNamespaceKey(key),
								super.jsonFactory.createValue(nsMap.get(key)),
								false);
					}
				}
			}
		}

		if (this.removeRoot) {
			if (stack.size() <= 2) {
				return;
			}
		}
		if (stack.size() <= 1) {
			return;
		}
		JSONObject jo = (JSONObject) stack.pop();
		JSONObject parent = (JSONObject) stack.peek();
		int size = jo.size();
		String key = getTagKey(name);
		if (size == 0) {
			if (keepEmptyElement) {
				appendToParent(parent, key, jo);
			} else {
				if (this.closedElement2null) {
					if (closedTag) {
						appendToParent(parent, key,
								this.jsonFactory.createValue(null));
					} else {
						appendToParent(parent, key,
								this.jsonFactory.createValue(""));
					}
				} else {
					appendToParent(parent, key,
							this.jsonFactory.createValue(null));
				}
			}
		} else {
			appendToParent(parent, key, jo);
		}
	}

	public String getNamespaceKey(String key) {
		if (strictBadgerfish) {
			if (StringUtils.isEmpty(key)) {
				return Badgerfish.KEY_TEXT;
			} else {
				return key;
			}
		} else {
			JSONObject parent = (JSONObject) stack.peek();
			if (StringUtils.isEmpty(key)) {
				return Badgerfish.getBadgerfishKey(parent,
						XMLConstants.XMLNS_ATTRIBUTE);
			} else {
				return Badgerfish.getBadgerfishKey(parent,
						XMLConstants.XMLNS_ATTRIBUTE + ':' + key);
			}
		}
	}

	@Override
	public String getNamespaceKey(QName name) {
		if (strictBadgerfish) {
			if (StringUtils.isEmpty(name.getPrefix())) {
				return Badgerfish.KEY_TEXT;
			} else {
				return name.getPrefix();
			}
		} else {
			JSONObject parent = (JSONObject) stack.peek();
			return Badgerfish.getBadgerfishKey(parent,
					super.getNamespaceKey(name));
		}
	}

	@Override
	public String getAttributeKey(QName name) {
		return Badgerfish.KEY_PREFIX_ATTRIBUTE + super.getAttributeKey(name);
	}

	@Override
	public String getTagKey(QName name) {
		if (strictBadgerfish) {
			return super.getTagKey(name);
		} else {
			if (keepOrderOfMixedTexts) {
				JSONObject parent = (JSONObject) stack.peek();
				return Badgerfish.getBadgerfishKey(parent,
						super.getTagKey(name));
			} else {
				return super.getTagKey(name);
			}
		}
	}

	@Override
	public String getTextKey() {
		if (strictBadgerfish) {
			return Badgerfish.KEY_TEXT;
		} else {
			if (keepOrderOfMixedTexts) {
				JSONObject parent = (JSONObject) stack.peek();
				return Badgerfish.getBadgerfishKey(parent, Badgerfish.KEY_TEXT);
			} else {
				return Badgerfish.KEY_TEXT;
			}

		}
	}

	@Override
	public String getCdatakey() {
		if (strictBadgerfish) {
			return getTextKey();
		} else {
			if (keepOrderOfMixedTexts) {
				JSONObject parent = (JSONObject) stack.peek();
				return Badgerfish.getBadgerfishKey(parent, Badgerfish.KEY_CDATA);
			}else{
				return getTextKey();
			}
		}
	}

	/**
	 * @return the strictBadgerfish
	 */
	public boolean isStrictBadgerfish() {
		return strictBadgerfish;
	}

	/**
	 * @param strictBadgerfish
	 *            the strictBadgerfish to setHeader
	 */
	public void setStrictBadgerfish(boolean strictBadgerfish) {
		this.strictBadgerfish = strictBadgerfish;
	}

	/**
	 * @return the lightWeightXmlns
	 */
	public boolean isLightWeightXmlns() {
		return lightWeightXmlns;
	}

	/**
	 * @param lightWeightXmlns
	 *            the lightWeightXmlns to setHeader
	 */
	public void setLightWeightXmlns(boolean lightWeightXmlns) {
		this.lightWeightXmlns = lightWeightXmlns && this.strictBadgerfish;
	}

	/**
	 * 
	 * @return If keep order of mixed texts and elements in parent element
	 *         container
	 */
	public boolean isKeepOrderOfMixedTexts() {
		return keepOrderOfMixedTexts;
	}

	/**
	 * @param keepOrderOfMixedTexts
	 *            If setHeader it true, will keep order of mixed texts and elements in
	 *            parent element container. The generated JSON might contain
	 *            some keys with #order-number. e.g. {"book#1":{}},
	 *            {"$#3":"value"}
	 */
	public void setKeepOrderOfMixedTexts(boolean keepOrderOfMixedTexts) {
		this.keepOrderOfMixedTexts = keepOrderOfMixedTexts;
	}

}